#pragma once

#include "json/json.h"
#include "ColumnDef.h"
#include "TableSchema.h"
#include "json/value.h"
#include <any>
#include <fstream>
#include <filesystem>
#include <stdexcept>
#include <iostream>
#include <sys/select.h>

class Table 
{
private:
    std::filesystem::path database_path_;
    TableSchema schema_;
    Json::Value data_root_;

public:
    Table(const std::filesystem::path& database_path, std::string_view table_name): 
        database_path_(database_path),
        schema_(table_name)
    {
        std::filesystem::path schema_path = database_path / (schema_.name() + ".schema");
        std::ifstream file(schema_path);
        if (!file.is_open()) {
            throw std::runtime_error("Could not open schema file");
        }

        Json::Value schema_root;
        file >> schema_root;

        const Json::Value& columns_array = schema_root["columns"];
        for (const auto& column_obj : columns_array) {
            schema_.addColumn(
                ColumnDef(column_obj["name"].asString(), 
                stringToColumnType(column_obj["type"].asString())));
        }
    }

    void readDataFromDisk() 
    {
        std::filesystem::path data_path = database_path_ / (schema_.name() + ".data");
        std::ifstream in_file(data_path);
        if (!in_file.is_open()) {
            throw std::runtime_error("Could not open data file");
        }
        in_file >> data_root_;
    }

    void flushDataToDisk() 
    {
        std::filesystem::path data_path = database_path_ / (schema_.name() + ".data");
        std::ofstream out_file(data_path);
        if (!out_file.is_open()) {
            throw std::runtime_error("Could not open data file");
        }
        out_file << data_root_;
    }

    void insertRow(const std::vector<std::any>& values)
    {
        if (data_root_.empty()) {
            throw std::runtime_error("Data did not read from disk");
        }

        if (!data_root_.isMember("rows") && data_root_.isArray()) {
            data_root_["rows"] = Json::Value(Json::arrayValue);
        }

        Json::Value row_obj(Json::objectValue);
        for (size_t i = 0; i < schema_.columnCount(); ++i) {
            ColumnDef column_def = schema_.column(i);
            
            if (column_def.type() == ColumnType::INT) {
                row_obj[column_def.name()] = any_cast<int>(values[i]);
            } else if (column_def.type() == ColumnType::TEXT) {
                row_obj[column_def.name()] = any_cast<std::string>(values[i]);
            }
        }
        
        data_root_["rows"].append(row_obj);
    }

    void dumpRows() const 
    {
        if (data_root_.empty()) {
            throw std::runtime_error("Data did not read from disk");
        }

        for (const auto& row : data_root_["rows"]) {
            for (size_t i = 0; i < schema_.columnCount(); ++i) {
                ColumnDef column_def = schema_.column(i);
                
                if (column_def.type() == ColumnType::INT) {
                    std::cout << column_def.name() << "=" << row[column_def.name()].asInt() << " ";
                } else if (column_def.type() == ColumnType::TEXT) {
                    std::cout << column_def.name() << "=" << row[column_def.name()].asString() << " ";
                }
            }
            std::cout << std::endl;
        }
    }
};